package com.daffodil.workable.util;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;

import javax.annotation.PostConstruct;

import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.calendar.CronExpression;
import org.flowable.common.engine.impl.el.JuelExpression;
import org.flowable.common.engine.impl.el.VariableContainerWrapper;
import org.flowable.common.engine.impl.runtime.ClockReader;
import org.flowable.common.engine.impl.util.DefaultClockImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.daffodil.core.entity.Query;
import com.daffodil.util.StringUtils;
import com.daffodil.workable.entity.WorkableForm;
import com.daffodil.workable.entity.WorkableLine;
import com.daffodil.workable.entity.WorkableNode;
import com.daffodil.workable.entity.WorkableResult;
import com.daffodil.workable.factory.DefaultWorkableFactory;
import com.daffodil.workable.service.IWorkableFormService;
import com.daffodil.workable.service.IWorkableLineService;
import com.daffodil.workable.service.IWorkableNodeService;
import com.daffodil.workable.service.IWorkablePlanService;
import com.daffodil.workable.service.IWorkableResultService;

import lombok.extern.slf4j.Slf4j;

/**
 * -任务工作流辅助工具
 * @author yweijian
 * @date 2022年4月11日
 * @version 1.0
 * @description
 */
@Slf4j
@Component
public class WorkableUtils {
    
    private static ClockReader clockReader;
    
    private static DefaultWorkableFactory factory;
    
    private static IWorkableNodeService nodeService;
    
    private static IWorkableLineService lineService;
    
    private static IWorkableFormService formService;
    
    private static IWorkableResultService resultService;
    
    private static IWorkablePlanService planService;
    
    @Value("${spring.jackson.time-zone:GMT+8}")
    private TimeZone timeZone;
    
    @Autowired
    DefaultWorkableFactory workableFactory;
    
    @Autowired
    IWorkableNodeService workableNodeService;
    
    @Autowired
    IWorkableLineService workableLineService;
    
    @Autowired
    IWorkableFormService workableFormService;
    
    @Autowired
    IWorkableResultService workableResultService;
    
    @Autowired
    IWorkablePlanService workablePlanService;
    
    @PostConstruct
    public void init() {
        clockReader = new DefaultClockImpl(this.timeZone);
        factory = this.workableFactory;
        nodeService = this.workableNodeService;
        lineService = this.workableLineService;
        formService = this.workableFormService;
        resultService = this.workableResultService;
        planService = this.workablePlanService;
    }
    
    public static void run(WorkableNode workableNode, String runmode) {
        Assert.notNull(workableNode, "The WorkableNode is not allowed null");
        Assert.notNull(runmode, "The Workable runing mode is not allowed null");
        List<WorkableLine> lines = lineService.selectWorkableLineListByTaskId(workableNode.getTaskId());
        boolean correct = isCorrectWorkable(lines);
        if(correct) {
            String traceId = UUID.randomUUID().toString();
            buildingWorkableNode(workableNode);
            runningWorkableStrategy(traceId, workableNode, null, runmode);
        }else {
            if(log.isWarnEnabled()) {
                log.warn("工作任务流程图设计错误，不允许存在环形回路连线，taskId={}", workableNode.getTaskId());
            }
        }
    }
    
    /**
     * -对节点的连线进行校验避免环形节点导致任务死循环
     * @param lines
     * @return
     */
    public static boolean isCorrectWorkable(List<WorkableLine> lines) {
        if(StringUtils.isEmpty(lines) || lines.size() <= 0) {
            return true;
        }
        for(int i = 0; i < lines.size(); i++) {
            boolean verify = verifyWorkableLine(lines, i);
            if(!verify){
                return false;
            }
        }
        return true;
    }

    private static boolean verifyWorkableLine(List<WorkableLine> lines, int index) {
        WorkableLine line = lines.get(index);
        List<WorkableLine> targets = getTargetLinesByTargetId(line.getTargetId(), lines);
        return verifyWorkableLineTargets(line, targets, lines);
    }

    private static boolean verifyWorkableLineTargets(WorkableLine line, List<WorkableLine> targets, List<WorkableLine> lines) {
        List<WorkableLine> sources = getTargetLinesBySourceId(line.getSourceId(), lines);
        if(sources.size() <= 1){
            return true;
        }
        if(targets.size() > 0){
            for(int i = 0; i < targets.size(); i++){
                if(line.getSourceId() == targets.get(i).getTargetId()){
                    return false;
                }
                if(line.getSourceId().equals(targets.get(i).getTargetId()) && line.getTargetId().equals(targets.get(i).getSourceId())){
                    return false;
                }
                List<WorkableLine> target2s = getTargetLinesByTargetId(targets.get(i).getTargetId(), lines);
                boolean verify = verifyWorkableLineTargets(line, target2s, lines);
                if(!verify){
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * -获取指定源节点连线
     * @param targetId
     * @param lines
     * @return
     */
    private static List<WorkableLine> getTargetLinesBySourceId(String sourceId, List<WorkableLine> lines) {
        List<WorkableLine> sources = new ArrayList<WorkableLine>();
        lines.forEach(line -> {
            if(sourceId.equals(line.getSourceId())){
                sources.add(line);
            }
        });
        return sources;
    }
    
    /**
     * -获取指定目标节点连线
     * @param targetId
     * @param lines
     * @return
     */
    private static List<WorkableLine> getTargetLinesByTargetId(String targetId, List<WorkableLine> lines) {
        List<WorkableLine> targets = new ArrayList<WorkableLine>();
        lines.forEach(line -> {
            if(targetId.equals(line.getSourceId())){
                targets.add(line);
            }
        });
        return targets;
    }

    /**
     * -构建工作流节点树
     * @param workableNode
     */
    private static void buildingWorkableNode(WorkableNode workableNode) {
        if(StringUtils.isNotNull(workableNode)) {
            WorkableForm workableForm = formService.selectWorkableFormByTaskIdAndNodeId(workableNode.getTaskId(), workableNode.getNodeId());
            workableNode.setWorkableForm(workableForm);
            //判断节点输出是否已经设置 避免死循环
            if(StringUtils.isEmpty(workableNode.getTargetNodeList())) {
                setTargetLineAndNodeList(workableNode);
            }
        }
    }
    
    /**
     * -设置当前节点的下一连线和下一节点
     * @param workableNode
     */
    private static void setTargetLineAndNodeList(WorkableNode workableNode){
        Query<WorkableLine> query = new Query<WorkableLine>();
        WorkableLine line = new WorkableLine();
        line.setTaskId(workableNode.getTaskId());
        line.setSourceId(workableNode.getNodeId());
        query.setEntity(line);
        List<WorkableLine> targetLineList = lineService.selectWorkableLineList(query);
        List<WorkableNode> targetNodeList = null;
        
        if(StringUtils.isNotEmpty(targetLineList)) {
            targetNodeList = new ArrayList<WorkableNode>();
            for(WorkableLine workableLine : targetLineList) {
                WorkableNode node = nodeService.selectWorkableNodeByTaskIdAndNodeId(workableLine.getTaskId(), workableLine.getTargetId());
                if(StringUtils.isNotNull(node)) {
                    buildingWorkableNode(node);
                    targetNodeList.add(node);
                }
            }
        }
        workableNode.setTargetLineList(targetLineList);
        workableNode.setTargetNodeList(targetNodeList);
    }
    
    /**
     * -执行工作流策略任务
     * @param traceId 追踪标识
     * @param workableNode 任务节点
     * @param lastResult 上一节点执行结果
     */
    private static void runningWorkableStrategy(String traceId, WorkableNode workableNode, WorkableResult lastResult, String runmode) {
        WorkableResult curtResult = factory.createWorkableStrategy(workableNode.getModelId()).run(traceId, workableNode, lastResult);
        //记录工作流节点执行的结果
        curtResult.setRunmode(runmode);
        resultService.insertWorkableResult(curtResult);
        
        List<WorkableNode> targetNodeList = workableNode.getTargetNodeList();
        List<WorkableLine> targetLineList = workableNode.getTargetLineList();
        if(StringUtils.isNotEmpty(targetNodeList)) {
            for(WorkableNode targetNode : targetNodeList) {
                WorkableLine workableLine = targetLineList.stream().filter((line) -> {
                    return line.getSourceId().equals(workableNode.getNodeId()) && line.getTargetId().equals(targetNode.getNodeId());
                }).findFirst().get();
                
                Boolean condition = true;
                String expression = workableLine.getExpression();
                if(StringUtils.isNotEmpty(expression)) {
                    Map<String, Object> variables = curtResult.getVariables();
                    String value = getExpressionValue(expression, variables);
                    condition = Boolean.valueOf(value);
                }
                
                //满足连线的EL表达式条件
                if(condition) {
                    runningWorkableStrategy(traceId, targetNode, curtResult, runmode);
                }
            }
        }
        
    }
    
    /**
     * -获取EL表达式处理结果
     * @param text
     * @param variables
     * @return
     */
    public static String getExpressionValue(String text, Map<String, Object> variables) {
        JuelExpression expression = null;
        if(StringUtils.isNotEmpty(text) && StringUtils.isNotEmpty(variables)) {
            try {
                expression = (JuelExpression) factory.getExpressionManager().createExpression(text);
                VariableContainer variableContainer = new VariableContainerWrapper(variables);
                Object value = expression.getValue(variableContainer);
                return String.valueOf(value);
            }catch (Exception warn) {
                if(log.isWarnEnabled()) {
                    log.warn(warn.getMessage());
                }
            }
        }
        return text;
    }
    
    /**
     * -获取下一次执行的时间
     * @param expression
     * @param fromTime 
     * @return long
     */
    public static long getNextTriggerTime(String expression, Date fromTime) {
        try {
            return new CronExpression(expression, clockReader).getNextValidTimeAfter(fromTime).getTime();
        } catch (ParseException e) {
            log.error(e.getMessage(), e);
        }
        return 0L;
    }
    
    /**
     * -验证是否正确的cron表达式
     * @param expression
     * @return
     */
    public static boolean isValidExpression(String expression) {
        return CronExpression.isValidExpression(expression, clockReader);
    }

    public static IWorkableNodeService getNodeService() {
        return nodeService;
    }

    public static IWorkableLineService getLineService() {
        return lineService;
    }

    public static IWorkableFormService getFormService() {
        return formService;
    }

    public static IWorkableResultService getResultService() {
        return resultService;
    }

    public static IWorkablePlanService getPlanService() {
        return planService;
    }
    
}
